Contingut

Gràfics amb

Introducció

  • En R hi ha moltes llibreries per fer gràfics
  • R base té capacitat per fer gràfics simples
  • Per gràfics més avançats la llibreria més utilitzada és “ggplot2”
  • N’hi ha d’altres, com ara “plotly”.

Funció “plot”

plot(x = var.x, 
     y = var.y, 
     type = "tipus",
     col = "color",
     pch = "tipus de punt",
     cex = "mida del punt",
     lwd = "amplada de la línia",
     main = "títol",
     sub = "subtítol",
     xlab = "nom de l'eix x",
     ylab = "nom de l'eix y",
     ...)

Funció “plot”: diagrames de caixes

  • Si li passem una variable categòrica sap que ha de fer boxplots:
plot(df$Sex, df$Age, main = "diagrama de caixes",
     xlab = "sexe", ylab = "edat")

Funció “plot”: diagrames de punts

  • Si les dues variables són contínues fa un gràfic de punts:
plot(df$RestBP, df$MaxHR, pch = 20, col = "red", cex = 2.5,
     main = "diagrama de punts", xlab = "Rest Blood Pressure", ylab = "Max Heart Rate")

Histograma

hist(df$Age, main = "histograma",xlab = "Edat")

ggplot

Com funciona:

library(ggplot2)
ggplot(df, aes(x = Sex, y = Chol, colour = AHD)) + 
  geom_boxplot()

  • La funció ggplot() crea el gràfic i sempre necessita dos paràmetres:
    1. Les dades amb les que fer el gràfic: o ve ens vénen d’una pipe o bé les posem al principi
    2. Els estètics (aesthetics en anglès, d’aquí el nom): aquí hi posem quina variable volem a l’eix x i y, i si volem colors o formes diferents.
  • Per acabar, li diem quin tipus de gràfic volem, en aquest cas un diagrama de caixes (boxplot).

Ho mirem en el plot:

ggplot(df, # liver és el nom del dataframe
       aes(x = Sex, # Variable en l'eix x 
           y = Chol, # Variable en l'eix y
           colour = AHD # Color: alerta, perquè el color crea grups!
           )) + # fixeu-vos en el signe "+" per afegir més comandes a la instrucció
  geom_boxplot() # geom_alguna_cosa() crea el tipus de plot que vulguem, en aquest cas un boxplot. Per saber els tipus de plots que hi ha podem anar a https://ggplot2.tidyverse.org/reference/

Un altre exemple:

df %>%
  ggplot(aes(Age, RestBP, colour = AHD)) + 
  geom_point()

geom_point() veiem que ens fa gràfics de punts, els adequats quan tenim dues variables contínues.

Fixeu-vos que aquí hem fet servir la pipe.

En lloc dels boxplots, potser volem els violin plots:

ggplot(df, aes(x = Sex, y = RestBP, colour = AHD)) + 
  geom_violin() +
  theme_bw() + 
  labs(title = "Violin plot, que són molt monos", 
       subtitle = "I aquí hi va un subtítol si volem") + 
  xlab("Gènere") + 
  ylab("Ritme cardíac en repòs")

ggplot(df, aes(x = Sex, y = RestBP, colour = AHD, fill = AHD)) + 
  geom_violin(alpha = 0.7) + # aquesta alpha fa que el color no sigui sòlid
  theme_bw() + 
  labs(title = "Violin plot, que són molt monos", 
       subtitle = "I aquí hi va un subtítol si volem") + 
  xlab("Gènere") + 
  ylab("Ritme cardíac en repòs")

A partir d’aquí hi ha moltíssimes maneres de personalitzar un diagrama:

ggplot(df, aes(x = Age, y = MaxHR, colour = AHD, shape = Sex)) + 
  geom_point(size=1.5) + 
  labs(title = "Gràfic puc útil", 
       subtitle = "Però molt fàcil de fer") + 
  xlab("Edat") +
  ylab("Batecs per minut màxims") +
  geom_smooth(method = "lm", se = FALSE)

Fixeu-vos que colour i shape em creen “grups”, és a dir, que quan li demano que em faci rectes (geom_smooth), me les fa segons els grups que li he fet crear a aes. Això és molt important!

Veieu però que aquest gràfic és bastant horrible perquè hem volgut posar massa informació a la vegada. Ho podem separar de forma fàcil amb facet_wrap:

ggplot(df, aes(x = Age, y = MaxHR, colour = AHD)) + 
  geom_point(size=1.5) + 
  facet_wrap(~ Sex) + # Fixeu-vos amb la titlla per dir "en funció de"
  labs(title = "Gràfic més útil") + 
  xlab("Edat") +
  ylab("Batecs per minut màxims") +
  geom_smooth(method = "lm", se = FALSE)

Bastant millor! Ara per exemple veig que a les dones que tenen AHD els puja els batecs per minut màxims amb l’edat. Això no té bona pinta!

Estètica

Paletes de colors:

Imaginem que tinc aquest gràfic, que faig amb les dades de contaminació:

library(readr)
library(tidyr)
library(lubridate)
library(glue)
cont <- read_csv("input/contaminacio.csv", locale = locale(encoding="latin1")) %>%
  select(data, municipi, contaminant, unitats, starts_with("h")) %>%
  mutate(contaminant = glue("{contaminant} ({unitats})")) %>%
  pivot_longer(starts_with("h")) %>%
  drop_na() %>%
  mutate(Mes = month(floor_date(data, "month"), label = T, abbr = F)) %>%
  group_by(municipi, contaminant, Mes) %>%
  summarise(Mitjana = mean(value)) %>%
  ungroup() %>%
  mutate(across(where(is.character), as.factor))

Les dades estan així:

glimpse(cont)
## Rows: 204
## Columns: 4
## $ municipi    <fct> Reus, Reus, Reus, Reus, Reus, Reus, Reus, Reus, Reus, Reus~
## $ contaminant <fct> CO (mg/m3), CO (mg/m3), CO (mg/m3), CO (mg/m3), CO (mg/m3)~
## $ Mes         <ord> gener, febrer, març, abril, maig, juny, juliol, agost, set~
## $ Mitjana     <dbl> 0.2632612, 0.2777778, 0.2203893, 0.2254167, 0.2036339, 0.2~

Fem un gràfic per cada ciutat:

cont %>%
  ggplot(aes(x = Mes, y = Mitjana, colour = contaminant, group = contaminant)) + 
  geom_line() + 
  facet_wrap(~ municipi) + # Fixeu-vos amb la titlla per dir "en funció de"
  labs(title = "Gràfic de contaminació") + 
  xlab("") +
  ylab("Contaminació en les seves unitats")

No està malament, però els colors són una mica lletjos i com que són colors de contaminació, busquem colors que siguin més tètrics. Podem buscar una paleta de colors que estigui bé aquí i aquí (i altres llocs si busqueu). En aquest segon link veiem que hi ha una paleta que es diu inferno (del paquet viridis, que és un paquet de colors només) i com que això de la contaminació és bastant infernal, sembla adequat.

Agafem els colors d’aquesta paleta amb la funció inferno que té un paràmetre, que és el nombre de colors que volem (així els espaiarà correctament):

paleta <- viridis::inferno(length(unique(cont$contaminant)))

Li diem al nostre gràfic que faci servir aquests colors:

cont %>%
  ggplot(aes(x = Mes, y = Mitjana, colour = contaminant, group = contaminant)) + 
  geom_line() + 
  facet_wrap(~ municipi) + # Fixeu-vos amb la titlla per dir "en funció de"
  labs(title = "Gràfic de contaminació") + 
  xlab("") +
  ylab("Contaminació en les seves unitats") + 
  scale_color_manual(values = paleta)

Molt millor!

Com a recurs, també hi ha el paquet RColorBrewer, que té paletes útils. I també podem crear la nostra. Fixeu-vos que la paleta és simplement un vector de colors en format hexagesimal:

paleta
##  [1] "#000004FF" "#1B0C42FF" "#4B0C6BFF" "#781C6DFF" "#A52C60FF" "#CF4446FF"
##  [7] "#ED6925FF" "#FB9A06FF" "#F7D03CFF" "#FCFFA4FF"

Per tant res ens impedeix de crear un vector amb els colors que ens agradin i posar-los on vulguem.

Temes

Els colors de les línies no són les úniques coses que són lletjotes dels gràfics. Les estètiques així més generals els retoquem amb la funció theme(). Per exemple, ens agradaria posar els noms dels mesos en diagonal perquè es vegin bé, evidentment ningú se sap la comanda per fer això, així que ho busquem a stackoverflow i trobem que podem fer:

cont %>%
  ggplot(aes(x = Mes, y = Mitjana, colour = contaminant, group = contaminant)) + 
  geom_line() + 
  facet_wrap(~ municipi) + # Fixeu-vos amb la titlla per dir "en funció de"
  labs(title = "Gràfic de contaminació") + 
  xlab("") +
  ylab("Contaminació en les seves unitats") + 
  scale_color_manual(values = paleta) + 
  theme(axis.text.x = element_text(angle = 45,hjust=1))

Molt bé! Ara veig que no hem capitalitzat els noms; ho fem en un moment (puc aprofitar la pipe!)

ordre_mesos <- stringr::str_to_title(levels(cont$Mes))
cont %>%
  mutate(
    Mes = stringr::str_to_title(Mes),
    # Asseguro que quan els capitalitzo no es desordenin
    Mes = forcats::fct_relevel(Mes, ordre_mesos) 
    ) %>%
  ggplot(aes(x = Mes, y = Mitjana, colour = contaminant, group = contaminant)) + 
  geom_line() + 
  facet_wrap(~ municipi) + # Fixeu-vos amb la titlla per dir "en funció de"
  labs(title = "Gràfic de contaminació") + 
  xlab("") +
  ylab("Contaminació en les seves unitats") + 
  scale_color_manual(values = paleta) + 
  theme(axis.text.x = element_text(angle = 45,hjust=1))

Volem canviar també el títol de la llegenda per posar-lo en majúscula, fem servir la funció guides() (guide = llegenda):

cont %>%
  mutate(
    Mes = stringr::str_to_title(Mes),
    # Asseguro que quan els capitalitzo no es desordenin
    Mes = forcats::fct_relevel(Mes, ordre_mesos) 
    ) %>%
  ggplot(aes(x = Mes, y = Mitjana, colour = contaminant, group = contaminant)) + 
  geom_line() + 
  facet_wrap(~ municipi) + # Fixeu-vos amb la titlla per dir "en funció de"
  labs(title = "Gràfic de contaminació") + 
  xlab("") +
  ylab("Contaminació en les seves unitats") + 
  scale_color_manual(values = paleta) + 
  theme(axis.text.x = element_text(angle = 45,hjust=1)) + 
  guides(colour=guide_legend(title = "Contaminant"))

També hi ha temes complets, per no haver d’anar canviat les coses una a una, els podeu trobar aquí.

A mi el que m’agrada més és el theme_bw()

cont %>%
  mutate(
    Mes = stringr::str_to_title(Mes),
    # Asseguro que quan els capitalitzo no es desordenin
    Mes = forcats::fct_relevel(Mes, ordre_mesos) 
    ) %>%
  ggplot(aes(x = Mes, y = Mitjana, colour = contaminant, group = contaminant)) + 
  geom_line() + 
  facet_wrap(~ municipi) + # Fixeu-vos amb la titlla per dir "en funció de"
  labs(title = "Gràfic de contaminació") + 
  xlab("") +
  ylab("Contaminació en les seves unitats") + 
  scale_color_manual(values = paleta) + 
  theme(axis.text.x = element_text(angle = 45,hjust=1)) + 
  guides(colour=guide_legend(title = "Contaminant")) + 
  theme_bw()

Molt millor, però ens ha desaparegut lo dels noms de costat. Això és perquè els temes completes a vegades sobreescriuren les coses que nosaltres haguem posat a theme() (perquè ells també estan tocant theme). En aquest cas l’únic que hem de fer és canviar l’ordre:

cont %>%
  mutate(
    Mes = stringr::str_to_title(Mes),
    # Asseguro que quan els capitalitzo no es desordenin
    Mes = forcats::fct_relevel(Mes, ordre_mesos) 
    ) %>%
  ggplot(aes(x = Mes, y = Mitjana, colour = contaminant, group = contaminant)) + 
  geom_line() + 
  facet_wrap(~ municipi) + # Fixeu-vos amb la titlla per dir "en funció de"
  labs(title = "Gràfic de contaminació") + 
  xlab("") +
  ylab("Contaminació en les seves unitats") + 
  scale_color_manual(values = paleta) + 
  theme_bw() +
  theme(axis.text.x = element_text(angle = 45,hjust=1)) + 
  guides(colour=guide_legend(title = "Contaminant"))

Ja tenim un gràfic prou decent, però ara el veig petit. Per veure’l més gran al markdown hem de tocar la configuració del chunk. per exemple, podem posar out.widht=“100%” (fixeu-vos amb les cometes) perquè ocupi tota l’amplada.

cont %>%
  mutate(
    Mes = stringr::str_to_title(Mes),
    # Asseguro que quan els capitalitzo no es desordenin
    Mes = forcats::fct_relevel(Mes, ordre_mesos) 
    ) %>%
  ggplot(aes(x = Mes, y = Mitjana, colour = contaminant, group = contaminant)) + 
  geom_line() + 
  facet_wrap(~ municipi) + # Fixeu-vos amb la titlla per dir "en funció de"
  labs(title = "Gràfic de contaminació") + 
  xlab("") +
  ylab("Contaminació en les seves unitats") + 
  scale_color_manual(values = paleta) + 
  theme_bw() +
  theme(axis.text.x = element_text(angle = 45,hjust=1)) + 
  guides(colour=guide_legend(title = "Contaminant"))

plotly

Plotly és una “nova” llibreria que tranforma els gràfics a javascript i html i per tant són interactius (com si fossin una pàgina web). Es pot utilitzar tant en R com en python i s’està fent molt famosa perquè es poden fer gràfics interactius de forma molt simple.

No és tan potent (a nivell de quantitat de coses que es poden fer) com ggplot2.

Veiem un exemple:

library(plotly)
plot_ly(data = iris, x = ~Sepal.Length, y = ~Petal.Length)

Fixeu-vos que teniu comandes per fer zoom, baixar-ho en png, etc.

Fixeu-vos que la forma de les comandes és semblant però aquí fem servir la titlla davant de cada variable.

Coloregem segons el tipus de plantes:

plot_ly(data = iris, x = ~Sepal.Length, y = ~Petal.Length, color = ~Species)

També es poden fer servir colors de paletes, per exemple la Spectral de la ColorBrewer:

plot_ly(data = iris, x = ~Sepal.Length, y = ~Petal.Length, color = ~Species, colors = "Spectral")

Un altre exemple, amb les dades de covid:

start <- lubridate::today() - 30
p <- "https://analisi.transparenciacatalunya.cat/resource/jj6z-iyrp.json"
# Fixeu-vos amb les cometes simples de l'start! Això és perquè lo que passarà aquí 
# dins en el fons és un string i el Socrata ho ha de saber
q <- glue::glue("?$where= data > '{start}'")
covid <- RSocrata::read.socrata(glue::glue(p, q)) %>%
  # Treu totes les columnes que acaben amb "codi" (evidentment, 
  # ho podem fer llistant-les una per una també) i treu també 
  # el nom del districte
  select(-c(ends_with("codi"), districtedescripcio)) %>%
  mutate(
    # Converteix el número de casos a numèric
    numcasos = as.numeric(numcasos),
    # I tota la resta (que encara son caràcters) a factor
    across(where(is.character), as.factor)
  )

Les sumo totes i faig un gràfic d’evolució:

covid %>%
  group_by(data) %>%
  summarise(Total_casos = sum(numcasos)) %>%
  plot_ly(x = ~ data, y = ~ Total_casos, type = 'scatter', mode = 'lines+markers')

Es poden fer coses més complicades amb la funció add_trace. Per exemple, vull afegir les vacunes, que també trec de dades obertes:

p <- "https://analisi.transparenciacatalunya.cat/resource/irki-p3c7.json"
q <- glue::glue("?$where= data > '{start}'")
vac <- RSocrata::read.socrata(glue::glue(p, q)) %>%
  # Treu totes les columnes que acaben amb "codi" (evidentment, 
  # ho podem fer llistant-les una per una també) i treu també 
  # el nom del districte
  select(-c(ends_with("CODI"), districte)) %>%
  mutate(
    # Converteix el número de casos a numèric
    recompte = as.numeric(recompte),
    # I tota la resta (que encara son caràcters) a factor
    across(where(is.character), as.factor)
  )

tot <- covid %>%
  inner_join(vac, by = c("data" = "data", "municipidescripcio" = "municipi")) %>%
  group_by(data) %>%
  summarise(Total_casos = sum(numcasos), Total_vacunes = sum(recompte))
fig <- plot_ly(tot, x = ~data, y = ~Total_casos, name = 'Casos totals', 
               type = 'scatter', mode = 'lines+markers')
fig <- fig %>% add_trace(y = ~Total_vacunes, name = 'Vacunes totals', mode = 'lines+markers')
fig

Potser volem posar les vacunes en un segon eix, es fa amb la funció layout i l’argument yaxis:

fig <- plot_ly(tot, x = ~data, y = ~Total_casos, name = 'Casos totals', 
               type = 'scatter', mode = 'lines+markers')
fig <- fig %>% add_trace(y = ~Total_vacunes, name = 'Vacunes totals', mode = 'lines+markers', yaxis = "y2")
fig <- fig %>% layout(yaxis2 = list(overlaying = "y", side = "right"))
fig

Tot i que, com veieu, això no és molt recomanable.

Potser millor afegir les vacunes com a barres? També es pot fer:

fig <- plot_ly(tot, x = ~data, y = ~Total_casos, name = 'Casos totals', 
               type = 'scatter', mode = 'lines+markers')
fig <- fig %>% add_trace(y = ~Total_vacunes, name = 'Vacunes totals', type = 'bar', yaxis = "y2", opacity=0.6)
fig <- fig %>% layout(yaxis2 = list(overlaying = "y", side = "right"))
fig

També tenim l’opció de convertir un gràfic de ggplot a plotly:

pl <- cont %>%
  mutate(
    Mes = stringr::str_to_title(Mes),
    # Asseguro que quan els capitalitzo no es desordenin
    Mes = forcats::fct_relevel(Mes, ordre_mesos) 
    ) %>%
  ggplot(aes(x = Mes, y = Mitjana, colour = contaminant, group = contaminant)) + 
  geom_line() + 
  facet_wrap(~ municipi) + # Fixeu-vos amb la titlla per dir "en funció de"
  labs(title = "Gràfic de contaminació") + 
  xlab("") +
  ylab("Contaminació en les seves unitats") + 
  scale_color_manual(values = paleta) + 
  theme_bw() +
  theme(axis.text.x = element_text(angle = 45,hjust=1)) + 
  guides(colour=guide_legend(title = "Contaminant"))

ggplotly(pl)

Fixeu-vos però que no canvia els eixos quan fem desaparèixer línies; el podem fer originalment amb plotly fent servir la funció group_map de dplyr i subplot de plotly:

cont %>%
  mutate(
    Mes = stringr::str_to_title(Mes),
    # Asseguro que quan els capitalitzo no es desordenin
    Mes = forcats::fct_relevel(Mes, ordre_mesos) 
    ) %>%
  # Hem d'agrupar *abans* del gràfic perquè plotly ja vol veure
  # les dades agrupades en els grups que sortiran en el gràfic
  group_by(municipi) %>%
  group_map(~ plot_ly(data=., x = ~Mes, y = ~Mitjana, color = ~contaminant, 
                      type = "scatter", mode="lines"), keep=TRUE) %>%
  subplot(nrows = 1, shareX = TRUE, shareY=TRUE)